#pragma once

//根据题目list文件，加载所有的题目信息到内存中：首先题目不可能会很多，而且题目的信息并不会很大
//直接与数据交互的模块，涉及题库等

#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <cassert>
#include <fstream>
#include <cstdlib>
#include "../../comm/log.hpp"
#include "../../comm/util.hpp"

namespace ns_model
{
    using namespace ns_util;
    using namespace ns_log;
   
    struct Question{
        std::string number;//题目编号，唯一
        std::string title; //题目标题
        std::string star;  //难度:简单，中等，困难
        int cpu_limit;     //题目的时间要求
        int mem_limit;     //题目的空间要求
        std::string desc;  //题目描述
        std::string header;//预设给用户在线编辑器中的代码
        std::string tail;  //题目的测试代码，需要和header拼接

        std::string mode;//标明题目类型:IO或Interface
        std::string IO_Input_File;//测试IO型题目的输入重定向文件路径
        std::string IO_Output_File;//IO型题目的输出比对
    };
    
    const std::string webroot = "./wwwroot/";
    const std::string question_path = "./questions/";//题目目录路径
    const std::string question_list_path = "./questions/questions.list";//题目列表具体路径
    const std::string question_sep = " ";//题目列表中一行题目信息的分隔符
    const std::string question_desc = "desc.txt";
    const std::string question_header = "header.cpp";
    const std::string question_tail = "tail.cpp";
    const std::string question_input = "InputFile.txt";
    const std::string question_output = "OutputFile.txt";
    const std::string question_path_IO = "../oj_server/questions/";//由于需要在run模块中找到,因此特别设置
    //与数据进行交互
    class Model
    {
    private:
        std::unordered_map<std::string,Question> questions;//使用题目编号作为key:可以实现通过题目编号的快速查找--非有序
        std::vector<Question> ordered_questions;//存储有序的题目,以便前端展示有序的题目列表
    public:
        Model()
        {
            assert(LoadQuestionList(question_list_path));
        }
        
        //加载所有的题目信息到内存中
        bool LoadQuestionList(const std::string &question_list)
        {
            //加载配置文件：题目列表文件+具体题目信息
            std::ifstream in(question_list);
            if(!in.is_open())
            {
                LOG(LogLevel::FATAL) << "加载题库文件失败,请检查是否存在题库文件";
                return false;
            }
            //读取question_list中每行信息：按行读取
            std::string line;
            //编号，名称，难度，时间，空间
            while(std::getline(in,line))
            {
                std::vector<std::string> tokens;
                StringUtil::SplitString(line,tokens,question_sep);
                Question q;
                if(tokens.size() != 6)
                {
                    LOG(LogLevel::WARNING) << "加载题库内容部分失败,请检查题库";
                    continue;
                }
                q.number = tokens[0];
                q.title = tokens[1];
                q.star = tokens[2];
                q.mode = tokens[3];//读取题目模式
                q.cpu_limit = atoi(tokens[4].c_str());
                q.mem_limit = atoi(tokens[5].c_str());

                std::string question_number_path = question_path + q.number;
                question_number_path += "/";
                //将题目的具体描述，header代码，tail代码都读取出来
                q.desc = FileUtil::ReadFile(question_number_path + question_desc);
                q.header = FileUtil::ReadFile(question_number_path + question_header);
                if(q.mode == "Interface")
                    q.tail = FileUtil::ReadFile(question_number_path + question_tail);   
                if(q.mode == "IO")
                {
                    q.IO_Input_File = question_path_IO + q.number + "/" + question_input; 
                    q.IO_Output_File = question_path_IO + q.number + "/" + question_output;
                }
                questions.insert({q.number,q});//此处未转移资源
                ordered_questions.push_back(std::move(q));
            }
            LOG(LogLevel::INFO) << "加载题库全部成功";
            in.close();
            return true;
        }

        //获取所有问题
        bool GetAllQuestions(std::vector<Question>& out)
        {
            if(questions.size() == 0)
            {
                LOG(LogLevel::ERROR) << "用户获取所有问题失败";
                return false;
            }
            for(const auto& e : ordered_questions)
                out.push_back(e);
            return true;   
        }

        //获取一个问题
        bool GetOneQuestion(const std::string &number,Question& q)
        {
            const auto& iter = questions.find(number);
            if(iter == questions.end())
            {
                LOG(LogLevel::ERROR) << "用户获取一个问题失败";
                return false;
            }
            q = iter->second;
            return true;
        }

        ~Model()
        {}
    };
}

